/** @file    FestivalImpl.cpp
 *  @author  Alessandro Polo
 *  @version $Id: FestivalImpl.cpp 2821 2010-08-01 11:44:26Z alex $
 *  @brief
 * File containing methods for the FestivalImpl class.
 * The header for this class can be found in FestivalImpl.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "FestivalImpl.h"

 #include <festival/festival.h>
 #include <interfaces/services/Festival.h>
 #include <core/Message.h>
 #include <core/Response.h>
 #include <core/FileSystem.h>
 #include <core/SecurityManager.h>

using namespace std;
using namespace wosh;

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

FestivalImpl::FestivalImpl( BundleGeneric& bundle )
	: BundleGenericWorker(bundle) {

 }


FestivalImpl::~FestivalImpl() {
	if ( isThreadRunning() ) {
		quitThread();
	 }
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////// RUN THREAD

void FestivalImpl::runThread()
 {
	int heap_size = 210000;  // default scheme heap size
	int load_init_files = 1; // we want the festival init files loaded

	festival_initialize(load_init_files,heap_size);

	execFestivalCommand( "(voice_us1_mbrola)" );

	while( this->running )
	 {
		ThreadImpl::sleepForMSec(100);
		setThreadAlive();

		this->jobs.transactionBeginWrite();
		if ( this->jobs.size() == 0 ) {
			this->jobs.transactionEnd();
			ThreadImpl::sleepForMSec(100);
			continue;
		 }

		FestivalJob* job = this->jobs.pop(0);
		this->jobs.transactionEnd();

		processJob(job);

		if ( job != NULL )
			delete job;
	 }

	// festival_say_file puts the system in async mode so we better
	// wait for the spooler to reach the last waveform before exiting
	// This isn't necessary if only festival_say_text is being used (and
	// your own wave playing stuff)
	festival_wait_for_spooler();

	Log(LOG_VERBOSE, ":runThread(): EXITING" );
 }

////////////////////////////////////////////////////////////////////////////////////////////////// RUN THREAD

void FestivalImpl::processJob( FestivalJob* job )
 {
	if ( job == NULL ) return;
	if ( job->conversion == NULL ) {
		if ( !job->festivalCommand.empty() )
			processJobConfiguration( job->festivalCommand, job->requestSource );
	 }
	else {
		if ( job->conversion->getOriginal() != NULL )
			processJobConversion( job->conversion, job->requestSource );
	 }
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void FestivalImpl::processJobConversion( NotificationConversion* conversion, const URI& requestSource )
 {
	if ( conversion->getOriginal() == NULL ) return;
	if ( conversion->getOriginal()->getPlain().empty() ) {
		Log(LOG_CRITICAL, ":processJob(): %ld is EMPTY!", conversion->getOriginal()->getID() );
		return;
	 }

	Log(LOG_INFO, ":processJob(): Converting %ld", conversion->getOriginal()->getID() );

	AudioMessage* aMsg = new AudioMessage();
	aMsg->fromNotification( conversion->getOriginal() );
	aMsg->setAudioType(AudioMessage::AUDIO_TEXT);

	string filename_sh = "$SHARED_TEMP/" + Utilities::toString<long>(aMsg->getID()) + ".wav";
	aMsg->setFile(filename_sh);
	string filename = filename_sh;
	FilePath::resolve( filename);

	//printf("\nFestivalImpl::processJob()\n%s\n", conversion->getOriginal()->getPlain().c_str() );
	// Convert to a waveform
	EST_Wave wave;
	Log(LOG_VERBOSE, ":processJob(): TTS %ld [%d chars]", conversion->getOriginal()->getID(), conversion->getOriginal()->getPlain().size() );
	int ret = festival_text_to_wave( conversion->getOriginal()->getPlain().c_str(), wave );
	if ( ret == 0 ) {
		Log(LOG_CRITICAL, ":processJob(): FAILED  TTS %ld", conversion->getOriginal()->getID() );
	 }
	Log(LOG_VERBOSE, ":processJob(): Saving %ld to %s [%s]", conversion->getOriginal()->getID(), filename.c_str(), filename_sh.c_str() );
	EST_write_status saved = wave.save( filename.c_str(), "riff" );
	if ( saved != write_ok ) {
		Log(LOG_CRITICAL, ":processJob(): FAILED Saving %ld to %s [%s]", conversion->getOriginal()->getID(), filename.c_str(), filename_sh.c_str() );
	 }
	Message* message = new Message( aMsg );
	message->setSource(this->getOwner()->getURI());
	if ( requestSource.isEmpty() )
		message->getDestination().fromString("*:Communication");
	else
		message->setDestination( requestSource );

	SecurityManager::getCredentialImplicit().signMessage(message, conversion->getOriginal()->getSender_User());

	Log(LOG_VERBOSE, ":processJob(): Replying %ld to %s", conversion->getOriginal()->getID(), requestSource.toString().c_str() );

	BusCore.postMessage(message);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void FestivalImpl::processJobConfiguration( const string& command, const URI& requestSource )
 {
	WRESULT ret = execFestivalCommand(command);

	Message* message = new Message( new Response(_Festival_METHOD_evalcmd, NULL, ret) );
	message->setSource(this->getOwner()->getURI());
	if ( requestSource.isEmpty() )
		message->getDestination().fromString("*:Communication");
	else
		message->setDestination( requestSource );

	if ( this->getOwner() != NULL )
		SecurityManager::getCredentialImplicit().signMessage(message, this->getOwner());

	Log(LOG_VERBOSE, ":processJob(): Replying configuration to %s", requestSource.toString().c_str() );

	BusCore.postMessage(message);
 }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

void FestivalImpl::enqueue( FestivalJob* job )
 {
	if ( job == NULL ) return;
	if ( job->conversion == NULL ) return;
	if ( job->conversion->getOriginal() == NULL ) return;
	if ( job->conversion->getOriginal()->getPlain().empty() ) {
		Log(LOG_CRITICAL, ":processJob(): %ld is EMPTY!", job->conversion->getOriginal()->getID() );
		return;
	 }

	Log(LOG_INFO, ":enqueue(): Enqueued %ld", job->conversion->getOriginal()->getID() );

	this->jobs.append(job);
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

WRESULT FestivalImpl::execFestivalCommand( const string& command )
 {
	int ret = festival_eval_command( command.c_str() );
	if ( ret == 1 )
		return WRET_OK;
	return WRET_ERR_INTERNAL;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////


/*
void FestivalImpl::listVoices()
 {

(set! voice_default 'voice_ked_diphone)
(voice.list)
(voice_us2_mbrola)
libdir

------------------------------

string(festival_version)



 }
*/


//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////


/*
//http://www.cstr.ed.ac.uk/projects/festival/manual/festival_28.html


	// Say simple file
	festival_say_file("/etc/motd");

	festival_eval_command("(voice_ked_diphone)");
	// Say some text;
	festival_say_text("hello world");

*/
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////



//////////////////////////////////////////////////////////////////////////////////////////////////////////////

